#include "b_local.h"


/*================================//
		NDA code for server
//================================*/
//
//	CONCEPTS:
//	A spell is "active" since is casted to when it's cooldown time is over. So the FINAL END of a spell is when it's cooldown period is over.
//	The effect of the spell happens when it has reached the "spell.endTime". Is the end of the CASTING TIME, and only the castin time.
//	Buff time is apart of the spell being active or not BUT the spell "active" time is ALWAYS greater than the buff time.
//	Why? Because if not the players would be able to cast unlimited buffs on theirselves.
//
//	*activeSpell: is the current spell that the player is casting.
//
//================================//
static	vec3_t	forward, vright, up;
static	vec3_t	muzzle;
// Variable used for the buff sound:
qboolean buffSoundPlayed = qfalse;
// Variables to syncronice the spell states:
enum 
{	ATTACK_BEAM_START,
	ATTACK_BEAM_CHARGED,
	SHIELD_START,
	SHIELD_END,
	HEAL_START,
	HEAL_END,
	SPELL_END};
// Variables for the arrow combination:
enum {NONE,RESET,DRAW_UP,DRAW_DOWN,DRAW_LEFT,DRAW_RIGHT};
typedef enum {UP,DOWN,LEFT,RIGHT} selection;
static qboolean firstTimeFwr = qtrue;
static qboolean firstTimeRight = qtrue;
static signed char oldUserCmdFwrd = 0;
static signed char oldUserCmdRight = 0;
// Array that records the combination:
static selection combo[6];
// Variable used to fill the combo array:
static int slot = 0;
// Saves the active spell:
static int activeSpell = -1;
// Time limit before the combination is considered unfinished:
static int comboTimeLimit;
// Time for reseting the unfinished combo:
static int COMBO_TIME_TO_RESET = 1000;	// 1 second

/*
==============
G_InitSpells

Initializes all the element info
==============
*/
//--------------------------------------------------------------
static void G_InitSpells( gentity_t *self ){
//--------------------------------------------------------------
	//Elemental_Attack_Beam
	self->client->elements[ELEMENT_ATTACK_BEAM].duration = 3000;	// Beam lasts 3 seconds.
	self->client->elements[ELEMENT_ATTACK_BEAM].charge = 1500;	// Charging period lasts 1.5 seconds.
	self->client->elements[ELEMENT_ATTACK_BEAM].cooldown = 10000;	// Cooldown period lasts 10 seconds.
	self->client->elements[ELEMENT_ATTACK_BEAM].endTime = 0;
	self->client->elements[ELEMENT_ATTACK_BEAM].endCooldown = 0;
	self->client->elements[ELEMENT_ATTACK_BEAM].active = qfalse;
	//Elemental_Shield
	self->client->elements[ELEMENT_SHIELD].duration = 3;			// Shield lasts 3 seconds.
	self->client->elements[ELEMENT_SHIELD].charge = 2000;			// Charging period lasts 2 seconds.
	self->client->elements[ELEMENT_SHIELD].cooldown = 20000;		// Cooldown period lasts 20 seconds.
	self->client->elements[ELEMENT_SHIELD].endTime = 0;
	self->client->elements[ELEMENT_SHIELD].endCooldown = 0;
	self->client->elements[ELEMENT_SHIELD].buffDuration = 15000;	// Buff duration lasts 15 seconds-
	self->client->elements[ELEMENT_SHIELD].buffEndTime = 0;
	self->client->elements[ELEMENT_SHIELD].active = qfalse;
	//Elemental_Heal
	self->client->elements[ELEMENT_HEAL].duration = 2000;			// Heal lasts 2 seconds.
	self->client->elements[ELEMENT_HEAL].charge = 1000;			// Charging period lasts 1 seconds.
	self->client->elements[ELEMENT_HEAL].cooldown = 30000;		// Cooldown period lasts 30 seconds.
	self->client->elements[ELEMENT_HEAL].endTime = 0;
	self->client->elements[ELEMENT_HEAL].endCooldown = 0;
	self->client->elements[ELEMENT_HEAL].active = qfalse;
}

/*
==============
AbleToCast

Returns if the player is able to cast.
==============
*/
//--------------------------------------------------------------
static qboolean AbleToCast( gentity_t *self ){
//--------------------------------------------------------------
	if ( !self ) {
		return qfalse;
	}

	if ( !self->client ) {
		return qfalse;
	}

	if ( self->client->ps.weapon != WP_SABER) {
		return qfalse;
	}

	if ( self->health <= 0 )
	{//if is in air, or is death
		return qfalse;
	}

	if (self->client->ps.pm_flags & PMF_FOLLOW)
	{ //not a "real" game client, it's a spectator following someone
		return qfalse;
	}

	if (self->client->sess.sessionTeam == TEAM_SPECTATOR) {
		return qfalse;
	}
	if (self->client->ps.weapon != WP_SABER)
	{ //can't use any of your magic while on an emplaced weapon
		return qfalse;
	}

	if (self->client->ps.m_iVehicleNum || self->client->jetPackOn)
	{ //can't use magic while riding a vehicle
		return qfalse;
	}

	if (self->client->ps.duelInProgress)
	{ //no magic on duels
		return qfalse;
	}

	if (self->client->ps.fallingToDeath)
	{ 
		return qfalse;
	}
	if ((self->client->ps.saberLockFrame) || (self->client->ps.saberLockTime > time))
	{
		return qfalse;
	}
	if ((self->client->ps.brokenLimbs & (1 << BROKENLIMB_RARM)) ||
		(self->client->ps.brokenLimbs & (1 << BROKENLIMB_LARM)))
	{ //spells we can't use with a broken arm
		return qfalse;
	}

	return qtrue;
}

/*
==============
SwitchElement

Switches an element from the combo array,
==============
*/
//--------------------------------------------------------------
static int SwitchElement( gentity_t *self ){
//--------------------------------------------------------------
	// Once finished the spell combo the player can cast again:
	if ((combo[0] == UP)&&(combo[1] == DOWN)&&(combo[2] == UP)&&(combo[3] == DOWN)&&(combo[4] == LEFT)&&(combo[5] == RIGHT))
	{// ATTACK BEAM:
		activeSpell = ELEMENT_ATTACK_BEAM;
	}
	if ((combo[0] == UP)&&(combo[1] == LEFT)&&(combo[2] == DOWN)&&(combo[3] == RIGHT)&&(combo[4] == UP)&&(combo[5] == DOWN))
	{// SHIELD:
		activeSpell = ELEMENT_SHIELD;
	}
	if ((combo[0] == UP)&&(combo[1] == DOWN)&&(combo[2] == LEFT)&&(combo[3] == UP)&&(combo[4] == DOWN)&&(combo[5] == RIGHT))
	{// HEAL:
		activeSpell = ELEMENT_HEAL;
	}
	return activeSpell;
}

//---------------------------------------------------------
static void WP_DisruptorMainFire( gentity_t *ent )
//---------------------------------------------------------
{
	int			damage = 100;
	qboolean	render_impact = qtrue;
	vec3_t		start, end;
	trace_t		tr;
	gentity_t	*traceEnt, *tent;
	float		shotRange = 8192;
	int			ignore, traces;

	memset(&tr, 0, sizeof(tr)); //to shut the compiler up

	VectorCopy( ent->client->ps.origin, start );
	start[2] += ent->client->ps.viewheight;//By eyes

	VectorMA( start, shotRange, forward, end );

	ignore = ent->s.number;
	traces = 0;
	while ( traces < 10 )
	{//need to loop this in case we hit a Jedi who dodges the shot
		if (d_projectileGhoul2Collision.integer)
		{
			trap_G2Trace( &tr, start, NULL, NULL, end, ignore, MASK_SHOT, G2TRFLAG_DOGHOULTRACE|G2TRFLAG_GETSURFINDEX|G2TRFLAG_THICK|G2TRFLAG_HITCORPSES, g_g2TraceLod.integer );
		}
		else
		{
			trap_Trace( &tr, start, NULL, NULL, end, ignore, MASK_SHOT );
		}

		traceEnt = &g_entities[tr.entityNum];

		if (d_projectileGhoul2Collision.integer && traceEnt->inuse && traceEnt->client)
		{ //g2 collision checks -rww
			if (traceEnt->inuse && traceEnt->client && traceEnt->ghoul2)
			{ //since we used G2TRFLAG_GETSURFINDEX, tr.surfaceFlags will actually contain the index of the surface on the ghoul2 model we collided with.
				traceEnt->client->g2LastSurfaceHit = tr.surfaceFlags;
				traceEnt->client->g2LastSurfaceTime = level.time;
			}

			if (traceEnt->ghoul2)
			{
				tr.surfaceFlags = 0; //clear the surface flags after, since we actually care about them in here.
			}
		}

		if (traceEnt && traceEnt->client && traceEnt->client->ps.duelInProgress &&
			traceEnt->client->ps.duelIndex != ent->s.number)
		{
			VectorCopy( tr.endpos, start );
			ignore = tr.entityNum;
			traces++;
			continue;
		}

		else if (traceEnt && traceEnt->client && traceEnt->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE] >= FORCE_LEVEL_3)
		{
			if (WP_SaberCanBlock(traceEnt, tr.endpos, 0, MOD_DISRUPTOR, qtrue, 0))
			{ //broadcast and stop the shot because it was blocked
				gentity_t *te = NULL;

				tent = G_TempEntity( tr.endpos, EV_DISRUPTOR_MAIN_SHOT );
				VectorCopy( muzzle, tent->s.origin2 );
				tent->s.eventParm = ent->s.number;

				te = G_TempEntity( tr.endpos, EV_SABER_BLOCK );
				VectorCopy(tr.endpos, te->s.origin);
				VectorCopy(tr.plane.normal, te->s.angles);
				if (!te->s.angles[0] && !te->s.angles[1] && !te->s.angles[2])
				{
					te->s.angles[1] = 1;
				}
				te->s.eventParm = 0;
				te->s.weapon = 0;//saberNum
				te->s.legsAnim = 0;//bladeNum

				return;
			}
		}
		else if ( (traceEnt->flags&FL_SHIELDED) )
		{//stopped cold
			return;
		}
		//a Jedi is not dodging this shot
		break;
	}

	if ( tr.surfaceFlags & SURF_NOIMPACT ) 
	{
		render_impact = qfalse;
	}

	// always render a shot beam, doing this the old way because I don't much feel like overriding the effect.
	/*tent = G_TempEntity( tr.endpos, EV_DISRUPTOR_MAIN_SHOT );
	VectorCopy( muzzle, tent->s.origin2 );
	tent->s.eventParm = ent->s.number;*/

	traceEnt = &g_entities[tr.entityNum];

	if ( render_impact )
	{
		if ( tr.entityNum < ENTITYNUM_WORLD && traceEnt->takedamage )
		{
			if ( traceEnt->client && LogAccuracyHit( traceEnt, ent )) 
			{
				ent->client->accuracy_hits++;
			} 

			G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage, DAMAGE_NORMAL, MOD_DISRUPTOR );
			
			tent = G_TempEntity( tr.endpos, EV_DISRUPTOR_HIT );
			tent->s.eventParm = DirToByte( tr.plane.normal );
			if (traceEnt->client)
			{
				tent->s.weapon = 1;
			}
		}
		else 
		{
			 // Hmmm, maybe don't make any marks on things that could break
			tent = G_TempEntity( tr.endpos, EV_DISRUPTOR_SNIPER_MISS );
			tent->s.eventParm = DirToByte( tr.plane.normal );
			tent->s.weapon = 1;
		}
	}
}



/*
==============
RefreshBuffState

Refresh the buff state.
==============
*/
//--------------------------------------------------------------
static void RefreshBuffState( gentity_t *self )
{
	if ( (self->client->elements[ELEMENT_SHIELD].active == qtrue) && (self->client->elements[ELEMENT_SHIELD].buffEndTime > level.time) )
	{// Shield buff:
		gentity_t *tent;
		tent = G_TempEntity( self->client->ps.origin, EV_SHIELD_BUFF ); 
	}
}

/*
==============
RefreshCastingState

Refresh the casting state.
==============
*/
//--------------------------------------------------------------
static void RefreshCastingState( gentity_t *self )
//--------------------------------------------------------------
{
	int i = 0;
	// Control the flags for the ui gfx propper drawing:
	if (self->client->ps.ndaFlags == SPELL_END)
		self->client->ps.ndaFlags = -1;
	// Control to reset the spell start flags:
	if ( self->client->ps.ndaFlags == SHIELD_START 
	|| self->client->ps.ndaFlags == HEAL_START 
	|| self->client->ps.ndaFlags == ATTACK_BEAM_START)
		self->client->ps.ndaFlags = -1;
	// Control for the active spell (if there's one)
	if ( activeSpell != -1 )
	{
		if (self->client->elements[activeSpell].active == qtrue)
		{
			if (activeSpell == ELEMENT_SHIELD)
			{// Play Shield events:
				gentity_t *tent;
				tent = G_TempEntity( self->client->ps.origin, EV_SHIELD ); 
			} else if (activeSpell == ELEMENT_HEAL)
			{// Play Heal events:
				gentity_t *tent;
				tent = G_TempEntity( self->client->ps.origin, EV_HEAL ); 
			} else if (activeSpell == ELEMENT_ATTACK_BEAM)
			{// Play Attack Beam events:
				gentity_t *tent;
				tent = G_TempEntity( self->client->ps.origin, EV_ATTACK_BEAM );
			}
			if (self->client->elements[activeSpell].endTime < level.time)
			{// Things that happen when a spell ends:
				switch ( activeSpell ) {
					case ELEMENT_SHIELD:
						self->client->ps.ndaBuffFlags = 1;
						self->client->elements[activeSpell].buffEndTime = level.time + self->client->elements[activeSpell].buffDuration;  
						break;
					case ELEMENT_HEAL:
						self->health = 100;
						G_Sound( self, CHAN_BODY, G_SoundIndex("sound/weapons/force/heal.wav") );
						break;
					default:
						break;
				}
				//Spell finished - can launch another one again:
				self->client->ps.ndaFlags = SPELL_END;
				self->client->ps.ndaElement = activeSpell = -1;
			} 
			else if (self->client->elements[activeSpell].chargeTime < level.time )
			{// Not finished, reached the charge time?:
				if( activeSpell == ELEMENT_ATTACK_BEAM && self->client->ps.ndaFlags != ATTACK_BEAM_CHARGED )
				{
					self->client->ps.ndaFlags = ATTACK_BEAM_CHARGED;
					G_Sound( self, CHAN_BODY, G_SoundIndex("sound/Element_MCp/MCp_DivineBuster.mp3") );
				} 
			}
			if (self->client->elements[activeSpell].chargeTime < level.time && activeSpell!=-1)
			{// if charged
				switch ( activeSpell ) {
					case ELEMENT_ATTACK_BEAM:
						//WP_DisruptorMainFire(self);
						break;
					default: 
						break; 
				}
			}
		}
	}
	// Control for each casted spell:
	for ( i=0 ; i<MAX_SPELLS ; i++ )
	{
		if ( (self->client->elements[i].active == qtrue) && (i != activeSpell) )
		{// Control if the spell is active and is not the actual spell (activeSpell)
			if ( self->client->elements[i].endCooldown < level.time )
			{// Control if cooldown has finished
				self->client->elements[i].active = qfalse;
				if (i == ELEMENT_ATTACK_BEAM) {
					trap_SendServerCommand( self->client->ps.clientNum, "cp \"^7You can cast ^6Elemental Attack Beam^7 again.\n\"" ); 
				}
				else if (i == ELEMENT_SHIELD) {
					trap_SendServerCommand( self->client->ps.clientNum, "cp \"^7You can cast ^2Elemental Shield^7 again.\n\"" );
				}
				else if (i == ELEMENT_HEAL) {
					trap_SendServerCommand( self->client->ps.clientNum, "cp \"^7You can cast ^1Elemental Heal^7 again.\n\"" );
				}
			}
			// Control if buff time finishes in 5 seconds, if so plays the shader:
			//if ( elements[ELEMENT_SHIELD].buffEndTime+5000 < level.time ) 
			// Control if buff time has finished:	
			if ( self->client->elements[ELEMENT_SHIELD].buffEndTime < level.time ) {
				self->client->ps.ndaBuffFlags = -1;
				buffSoundPlayed = qfalse;
			}
			else if (buffSoundPlayed == qfalse) {
			 	//G_Sound( self, CHAN_BODY, G_SoundIndex("sound/MG_shield/MG_shield_buff.wav") );
				buffSoundPlayed = qtrue;
			}
		}
	}
}

/*
==============
WP_SpellStart

Starts the spell.
==============
*/
//--------------------------------------------------------------
static void WP_SpellStart(gentity_t *self)
//--------------------------------------------------------------
{
	int hearDist;
	self->client->elements[activeSpell].active = qtrue;
	self->client->elements[activeSpell].endTime = level.time + self->client->elements[activeSpell].charge 
		+ self->client->elements[activeSpell].duration;
	self->client->elements[activeSpell].endCooldown = level.time + self->client->elements[activeSpell].charge
		+ self->client->elements[activeSpell].duration + self->client->elements[activeSpell].cooldown;
	switch(activeSpell){
		case ELEMENT_ATTACK_BEAM:
		{ 
			gentity_t *tent;
			self->client->elements[activeSpell].chargeTime = level.time + self->client->elements[activeSpell].charge;
			self->client->ps.ndaFlags = ATTACK_BEAM_START;
			tent = G_TempEntity( self->client->ps.origin, EV_ATTACK_BEAM );
			G_Sound( self, CHAN_BODY, G_SoundIndex("sound/weapons/scepter/recharge.mp3") );
			hearDist = 512;
			break;
		}
		case ELEMENT_SHIELD:
		{
			gentity_t *tent;
			self->client->elements[activeSpell].chargeTime = level.time + self->client->elements[activeSpell].charge;
			self->client->ps.ndaFlags = SHIELD_START;
			tent = G_TempEntity( self->client->ps.origin, EV_SHIELD );
			G_Sound( self, CHAN_BODY, G_SoundIndex("sound/MG_shield/MG_shield.mp3") );
			hearDist = 256;
			break;
		}
		case ELEMENT_HEAL:
		{
			gentity_t *tent;
			self->client->elements[activeSpell].chargeTime = level.time; // No charge for heal
			self->client->ps.ndaFlags = HEAL_START;
			tent = G_TempEntity( self->client->ps.origin, EV_HEAL );
			G_Sound( self, CHAN_BODY, G_SoundIndex("sound/MG_heal/MG_heal.mp3") );
			hearDist = 256;
			break;
		}
		default:
			break;
	}
	// Don't know if this does something:
	self->client->ps.otherSoundLen = hearDist;
	self->client->ps.otherSoundTime = level.time + 100;
}
/*
==============
WP_SpellUpdate

The engine of the magic system.
==============
*/
//--------------------------------------------------------------
void WP_SpellUpdate( gentity_t *self, usercmd_t *ucmd ){
//--------------------------------------------------------------

	if (AbleToCast(self)==qfalse)
		return;

	// If it's the first time, we have to fill the elements info:
	if (self->client->elements[0].duration==0)
		G_InitSpells(self);

	RefreshCastingState(self);
	RefreshBuffState(self);

	if (self->playerState->groundEntityNum == ENTITYNUM_NONE)
		return;
	
	if ( (ucmd->buttons & BUTTON_CAST_SPELL) && (self->client->ps.groundEntityNum != ENTITYNUM_NONE) )
	{// Is casting
		if (self->client->castCircleEndTime < level.time){
			gentity_t *tent;
			tent = G_TempEntity( self->client->ps.origin, EV_CASTING );
			self->client->castCircleEndTime = level.time+49;
		}
		self->client->ps.isCasting = 1;
		//TODO: can't jump & reset all the cinetic energy:
		ucmd->upmove = 0;

		//TODO: add UI graphics for the arrow combination:
  
		if (activeSpell == -1)
		{//If already launching a spell, player can't cast another one:
			if ( (slot>0) && (slot<6) && (level.time >= comboTimeLimit) )
			{// If reached combination reset deadline, reset "slot":
				G_Printf("\n^3Casting combo reseted.\n");
				self->client->ps.ndaComboFlags = RESET;
				slot=0;
			}
			else {
				if ( (ucmd->forwardmove == 0) && (ucmd->rightmove == 0) ) //both=0 means you're ONLY pressing cast button
				{//This make possible consecutive combinations of the same key:
					firstTimeFwr = qtrue;
					firstTimeRight = qtrue;
					self->client->ps.ndaComboFlags = NONE;
				}
				// Spell combinations:
				if ( (ucmd->forwardmove>0 && oldUserCmdFwrd<0) || (ucmd->forwardmove>0 && firstTimeFwr == qtrue) ){
					comboTimeLimit = level.time+COMBO_TIME_TO_RESET;
					firstTimeFwr = qfalse;
					oldUserCmdFwrd=ucmd->forwardmove;
					combo[slot] = UP;
					slot++;
					self->client->ps.ndaComboFlags = DRAW_UP;
				}
				if ( (ucmd->forwardmove<0 && oldUserCmdFwrd>0) || (ucmd->forwardmove<0 && firstTimeFwr == qtrue) ){
					comboTimeLimit = level.time+COMBO_TIME_TO_RESET;
					firstTimeFwr = qfalse;
					oldUserCmdFwrd=ucmd->forwardmove;
					combo[slot] = DOWN;
					slot++;
					self->client->ps.ndaComboFlags = DRAW_DOWN;
				}
				if ( (ucmd->rightmove>0 && oldUserCmdRight<0) || (ucmd->rightmove>0 && firstTimeRight == qtrue) ){
					comboTimeLimit = level.time+COMBO_TIME_TO_RESET;
					firstTimeRight = qfalse;
					oldUserCmdRight=ucmd->rightmove;
					combo[slot] = RIGHT;
					slot++;
					self->client->ps.ndaComboFlags = DRAW_RIGHT;
				}
				if ( (ucmd->rightmove<0 && oldUserCmdRight>0) || (ucmd->rightmove<0 && firstTimeRight == qtrue) ){
					comboTimeLimit = level.time+COMBO_TIME_TO_RESET;
					firstTimeRight = qfalse;
					oldUserCmdRight=ucmd->rightmove;
					combo[slot] = LEFT;
					slot++;
					self->client->ps.ndaComboFlags = DRAW_LEFT;
				}
				
				if ( slot == 6 )
				{
					self->client->ps.ndaElement = SwitchElement(self);
					if (self->client->ps.ndaElement == -1)
					{// Spell combination unrecognized:
						trap_SendServerCommand( self->client->ps.clientNum, "cp \"^1Your combo doesn't match any Spell.\n\"" );
						self->client->ps.ndaComboFlags = RESET;
					}
					else
					{// Spell combination recognized:
						if (self->client->elements[activeSpell].active != qtrue)
						{// If cooldown has finished:
							WP_SpellStart(self);
						}
						else
						{// We have to wait the cooldown.
							int waitTime=ceil(((self->client->elements[activeSpell].endCooldown)-level.time)/1000)+1;
							if (activeSpell == ELEMENT_ATTACK_BEAM) {
								G_Printf("You must wait %d seconds before casting Elemental Attack Beam again.\n", waitTime);
								self->client->ps.ndaComboFlags = RESET;
								self->client->ps.ndaElement = activeSpell = -1;
							}
							else if (activeSpell == ELEMENT_SHIELD) {
								G_Printf("You must wait %d seconds before casting Elemental Shield again.\n", waitTime);
								self->client->ps.ndaComboFlags = RESET;
								self->client->ps.ndaElement = activeSpell = -1;
							}
							else if (activeSpell == ELEMENT_HEAL) {
								G_Printf("You must wait %d seconds before casting Elemental Heal again.\n", waitTime);
								self->client->ps.ndaComboFlags = RESET;
								self->client->ps.ndaElement = activeSpell = -1;
							}
							slot=0;
						}
					}
					//Reset everything:
					slot=0;
					oldUserCmdFwrd=0;
					oldUserCmdRight=0;
				}
			}
		}
	}
	
	if ( (ucmd->buttons & BUTTON_CAST_SPELL) == 0 )
	{// If not casting
		self->client->ps.isCasting = 0;
		self->client->ps.ndaElement = -1;
		self->client->ps.ndaFlags = -1;
		self->client->ps.ndaComboFlags = -1;
		if (activeSpell != -1){
			self->client->elements[activeSpell].endTime = level.time;
			self->client->elements[activeSpell].endCooldown = level.time + self->client->elements[activeSpell].cooldown;
			self->client->ps.ndaFlags = SPELL_END;
			self->client->ps.ndaElement = activeSpell = -1; 
			trap_SendServerCommand( self->client->ps.clientNum, "cp \"^1Casting unfinished\n\"" );
		}
	}
}